/*
 * vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:
 *
 * Copyright (C) 2008, 2009 Mihai Şucan
 *
 * This file is part of libmacrame.
 *
 * Libmacrame is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Libmacrame is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Libmacrame.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $URL: http://code.google.com/p/libmacrame $
 * $Date: 2009-04-17 19:06:03 +0300 $
 *
 */

/**
 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 * @version Pre-alpha release. In-design phase, unstable API.
 * @fileOverview The libmacrame core code.
 */


(function () {

/*
 * This defines the global alias to use for the libmacrame object. Set this to 
 * false if you don't want any global object.
 */
var _alias = '$';

/**
 * The global libmacrame object which can't be changed, and which must be used 
 * by any plugins extending the functionality of the library.
 *
 * @function
 * @name libmacrame
 * @see $ By default, the entire libmacrame object is aliased by $, for quicker 
 * access.
 * @see $.init The function being called, when you call libmacrame().
 *
 * @param {String} selector
 * @param {Document|Element} [context=document]
 */
function libmacrame (selector, context) {
  return $.init(selector, context);
}

if (_alias) {
  window[_alias] = libmacrame;
}

window.libmacrame = libmacrame;

/**
 * The $ symbol is simply an alias of {@link libmacrame}, for quicker access to 
 * the main namespace and main function, which is {@link $.init}.
 *
 * @class The libmacrame namespace.
 * @name $
 * @see libmacrame
 * @see $.init This is the function called when you call $()
 *
 * @param {String} selector
 * @param {Document|Element} [context=document]
 */
var $ = libmacrame;

/*
 * This regular expression used to quickly match ID and class name CSS 
 * selectors.
 */
var re_id_class = /^(#|\.)?([a-zA-Z][a-zA-Z\d_-]*)$/;

/**
 * Find DOM elements.
 * 
 * <p>This function currently allows you to use CSS selectors to find the DOM 
 * elements you want.
 *
 * <p>TODO: This is not done yet.
 *
 * @requires A Web browser which implements <a 
 * href="http://www.w3.org/TR/selectors-api/">Selectors API</a>. Currently this 
 * means <a href="http://opera.com">Opera</a> 10+, <a 
 * href="http://mozilla.com">Firefox</a> 3.1+ and <a 
 * href="http://apple.com/safari">Safari</a> 4+.
 *
 * @example
 * // Find the element with id='foo':
 *
 * <code>$('#foo');</code>
 *
 * @example
 * // Find the elements having a class token 'foo':
 *
 * <code>$('.foo');</code>
 *
 * @example
 * // Find all the <code>&lt;span&gt;</code> elements which are direct child
 * // children of <code>&lt;p&gt;</code> elements:
 *
 * <code>$('p &gt; span');</code>
 *
 * @param {String} selector The CSS selector. You can also use multiple 
 * selectors, separated by comma. Basically, you can use anything supported by 
 * querySelectorAll().
 *
 * @param {Document|Element} [context=document] The context where the CSS 
 * selector will be used for finding the matching elements.
 *
 * @returns {Element|NodeList} The element found, or the list of elements 
 * matching the selector.
 */
$.init = function (selector, context) {
  if (!selector || typeof selector != 'string') {
    return selector;
  }

  if (!context) {
    context = document;
  }

  var elem = false,
      match = re_id_class.exec(selector);

  // If the selector is not an ID/class name, then just use the query selector.
  if (!match) {
    return context.querySelectorAll(selector);
  }

  if (match[1] == '#' && match[2]) {
    elem = context.getElementById(match[2]);
  } else if (match[1] == '.' && match[2]) {
    elem = context.getElementsByClassName(match[2]);
  } else if (match[2]) {
    elem = context.getElementsByTagName(match[2]);
  } else {
    elem = context.querySelectorAll(selector);
  }

  return elem;
};

/**
 * @namespace Holds browser information.
 */
$.browser = {};

var ua = '', b = {};

if (window.navigator && window.navigator.userAgent) {
  ua = window.navigator.userAgent.toLowerCase();
}

/**
 * Determine the browser version.
 *
 * @param {String} name The browser name you want to search for, in the User 
 * Agent string. The string is not escaped, thus you must use regular expression 
 * syntax.
 *
 * @param {Number} [pos=1] The index of the matching parenthesis in the regular 
 * expressiong. The value of the index will be returned by this function 
 * (ua.match(regex)).
 *
 * @returns {String|null} The matching version number.
 */
b.findVer = function (name, pos) {
  var regex = '\\b' + name + '[\\/: ]([0-9a-z.+-]+)';
  var res = ua.match(new RegExp(regex));
  if (!pos) {
    pos = 1;
  }

  return res ? res[pos] : null;
};

/**
 * @name $.browser.opera
 * @type Boolean
 */
b.opera = window.opera ? true : /\bopera\b/.test(ua);

/**
 * @name $.browser.operaVersion
 */
b.operaVersion = null;

if (window.opera && window.opera.version) {
  b.operaVersion = typeof window.opera.version == 'function' 
    ? window.opera.version() : window.opera.version;
} else if (b.opera) {
  b.operaVersion = b.findVer('opera');
}

/**
 * Webkit is the render engine used primarily by Safari. It's also used by 
 * Google Chrome and GNOME Epiphany.
 *
 * @name $.browser.webkit
 * @type Boolean
 */
b.webkit = /\b(applewebkit|webkit)\b/.test(ua);

/**
 * @name $.browser.webkitVersion
 */
b.webkitVersion = b.webkit ? b.findVer('(applewebkit|webkit)', 2) : null;

/**
 * Epiphany is the default Web browser of the GNOME desktop environment.
 *
 * @name $.browser.epiphany
 * @type Boolean
 */
b.epiphany = /\bepiphany\b/.test(ua);

/**
 * @name $.browser.epiphanyVersion
 */
b.epiphanyVersion = b.epiphany ? b.findVer('epiphany') : null;

/**
 * Firefox uses the Gecko render engine.
 *
 *
 * @name $.browser.firefox
 * @type Boolean
 */
// In some variations of the User Agent strings provided by Epiphany and Opera, 
// Firefox is mentioned.
b.firefox = /\bfirefox\b/.test(ua) && !b.opera && !b.epiphany;

/**
 * @name $.browser.firefoxVersion
 */
b.firefoxVersion = b.firefox ? b.findVer('firefox') : null;

/**
 * Gecko is the render engine used by Firefox and related products.
 *
 * @name $.browser.gecko
 * @type Boolean
 */
// Typically, the user agent string of WebKit also mentions Gecko. Additionally, 
// Opera mentions Gecko for tricking some sites.
b.gecko = /\bgecko\b/.test(ua) && !b.opera && !b.webkit;
// TODO: b.geckoRevision

/**
 * @name $.browser.geckoBuild
 */
b.geckoBuild = b.gecko ? b.findVer('gecko') : null;

/**
 * KHTML is the render engine used by Konqueror.
 *
 * @name $.browser.khtml
 * @type Boolean
 */
// Epiphany does mention KHTML...
b.khtml = /\bkhtml\b/.test(ua) && !b.epiphany;

/**
 * @name $.browser.khtmlVersion
 */
b.khtmlVersion = b.khtml ? b.findVer('khtml') : null;

/**
 * Konqueror is the Web browser of the KDE desktop environment.
 *
 * @name $.browser.konqueror
 * @type Boolean
 */
b.konqueror = /\bkonqueror\b/.test(ua);

/**
 * @name $.browser.konquerorVersion
 */
b.konquerorVersion = b.konqueror ? b.findVer('konqueror') : null;

/**
 * Google Chrome uses WebKit as the render engine.
 *
 * @name $.browser.chrome
 * @type Boolean
 */
b.chrome = /\bchrome\b/.test(ua);

/**
 * @name $.browser.chromeVersion
 */
b.chromeVersion = b.chrome ? b.findVer('chrome') : null;

/**
 * Microsoft Internet Explorer. The future of computing.
 *
 * @name $.browser.msie
 * @type Boolean
 */
// Again, Opera allows users to easily fake the UA.
b.msie = /\bmsie\b/.test(ua) && !b.opera;

/**
 * @name $.browser.msieVersion
 */
b.msieVersion = b.msie ? b.findVer('msie') : null;

/**
 * Presto is the render engine used by Opera.
 *
 * @name $.browser.presto
 * @type Boolean
 */
// Older versions of Opera did not mention Presto in the UA string.
b.presto = /\bpresto\b/.test(ua) || b.opera;

/**
 * @name $.browser.prestoVersion
 */
b.prestoVersion = b.presto ? b.findVer('presto') : null;

/**
 * @name $.browser.mozilla
 * @type Boolean
 */
// Everyone mentions Mozilla. Hehe.
b.mozilla = /\bmozilla\b/.test(ua) && !b.opera && !b.webkit && !b.presto;

/**
 * Safari is the default Web browser of Mac OS, from Apple. It uses the WebKit 
 * render engine.
 *
 * @name $.browser.safari
 * @type Boolean
 */
// Google Chrome mentions Safari and Epiphany does this as well.
b.safari = /\bsafari\b/.test(ua) && b.webkit && !b.epiphany && !b.chrome;

/**
 * @name $.browser.safariVersion
 */
b.safariVersion = b.safari ? b.findVer('safari') : null;


/**
 * The browser name. If the browser is unrecognized, it will contain the browser 
 * engine name. If all fails, the property will be null.
 *
 * @name $.browser.name
 * @type String
 */
// Here the order is important: start with the browsers, and end with the engine 
// names.
b.name = b.epiphany  ? 'epiphany'  : (
         b.firefox   ? 'firefox'   : (
         b.konqueror ? 'konqueror' : (
         b.opera     ? 'opera'     : (
         b.msie      ? 'msie'      : (
         b.chrome    ? 'chrome'    : (
         b.safari    ? 'safari'    : (
         b.presto    ? 'presto'    : (
         b.khtml     ? 'khtml'     : (
         b.webkit    ? 'webkit'    : (
         b.gecko     ? 'gecko'     : (
         b.mozilla   ? 'mozilla'   : null)))))))))));

/**
 * The version of the browser or render engine.
 *
 * @name $.browser.version
 * @type String
 */
b.version = b[b.name + 'Version'];

/**
 * Browser operating system
 *
 * @name $.browser.os
 * @type String
 */
b.os = (ua.match(/\b(windows|linux)\b/) || [])[1];

delete b.findVer;

$.browser = b;

delete b, ua;

/**
 * This function extends objects.
 *
 * @example
 * <code>var <var>obj1</var> = {a: 'a1', b: 'b1', d: 'd1'},
 *     <var>obj2</var> = {a: 'a2', b: 'b2', c: 'c2'};
 * 
 * $.extend(<var>obj1</var>, <var>obj2</var>);</code>
 * 
 * // Now <var>obj1.c == 'c2'</var>, while <var>obj1.a</var>, <var>obj1.b</var>
 * // and <var>obj1.d</var> remain the same.
 *
 * // If <code>$.extend(true, <var>obj1</var>, <var>obj2</var>)</code> is
 * // called, then <var>obj1.a</var>, <var>obj1.b</var>, <var>obj1.c</var>
 * // become all the same as in <var>obj2</var>.
 *
 * @example
 * <code>var <var>obj1</var> = {a: 'a1', b: 'b1', extend: $.extend};
 * <var>obj1</var>.extend({c: 'c1', d: 'd1'});</code>
 *
 * // In this case the destination object which is to be extend is
 * // <var>obj1</var>.
 *
 * @param {Boolean} [overwrite=false] If the first argument is a boolean, then 
 * it will be considered as a boolean flag for overwriting (or not) any existing 
 * methods and properties in the destination object. Thus, any method and 
 * property from the source object will take over those in the destination. The 
 * argument is optional, and if it's omitted, then no method/property will be 
 * overwritten.
 *
 * @param {Object} [destination=this] The second argument is the optional 
 * destination object: the object which will be extended. By default, the 
 * <var>this</var> object will be extended.
 *
 * @param {Object} source The third argument must provide list of methods and 
 * properties which will be added to the destination object.
 */
$.extend = function extend () {
  var i = 0,
      len = arguments.length,
      name, src, sval, dval;

  if (typeof arguments[0] == 'boolean') {
    force = arguments[0];
    dest  = arguments[1];
    src   = arguments[2];
  } else {
    force = false;
    dest  = arguments[0];
    src   = arguments[1];
  }

  if (typeof src == 'undefined') {
    src = dest;
    dest = this;
  }

  if (typeof dest == 'undefined') {
    return;
  }

  for (name in src) {
    sval = src[name];
    dval = dest[name];
    if (force || typeof dval == 'undefined') {
      dest[name] = sval;
    }
  }
};

})();

